package com.finger.gulimall.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.finger.common.to.es.SkuEsModel;
import com.finger.common.utils.R;
import com.finger.gulimall.search.config.GulimallSearchConfig;
import com.finger.gulimall.search.constant.ProductSearchConstant;
import com.finger.gulimall.search.feign.ProductRemoteService;
import com.finger.gulimall.search.service.MallSearchService;
import com.finger.gulimall.search.vo.SearchParam;
import com.finger.gulimall.search.vo.SearchResult;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class MallSearchServiceImpl implements MallSearchService {

    @Autowired
    private RestHighLevelClient client;

    @Autowired
    private ProductRemoteService productRemoteService;

    @Override
    public SearchResult search(SearchParam param) {
        SearchResult result = null;

        try {

            // 准备检索请求，封装
            SearchRequest request = buildSearchRequest(param);

            // 检索es信息
            SearchResponse searchResponse = client.search(request, GulimallSearchConfig.COMMON_OPTIONS);

            // 构建返回页面的结果
            result = buildSearchResponse(searchResponse, param);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return result;
    }

    /**
     * 构建页面传过来的查询条件
     *
     * @param param 页面传过来的查询参数
     * @return
     */
    public SearchRequest buildSearchRequest(SearchParam param) throws IOException {

        SearchRequest request = new SearchRequest();
        // 指明索引（即数据库）
        request.indices(ProductSearchConstant.PRODUCT_INDEX);

        // 相同于最外层的 {}
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        // 指明资源路径
        request.source(sourceBuilder);
        // 合并查询条件构造器
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        // !!把查询条件(bool)放入 sourceBuilder
        sourceBuilder.query(boolQueryBuilder);
        // 如果关键词不为空，做关键字模糊查询
        String keyword = param.getKeyword();
        if (!StringUtils.isEmpty(keyword)) {
            boolQueryBuilder.must(QueryBuilders.matchQuery("skuTitle", keyword));
        }

        // 如果菜单条件不为空，添加菜单条件
        Long catalog3Id = param.getCatalog3Id();
        if (catalog3Id != null) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("catelogId", catalog3Id));
        }

        // 如果品牌ids不为空，就按照品牌id term 查询
        List<Long> brandIds = param.getBrandId();
        if (brandIds != null && brandIds.size() > 0) {
            boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId", brandIds));
        }

        // 查询库存
        Integer hasStock = param.getHasStock();
        if (hasStock != null) {
            // 按照库存数量查询（0：没有库存，1：有库存），默认查有库存
            boolQueryBuilder.filter(QueryBuilders.termQuery("hasStock", param.getHasStock() == 1));
        }

        // 如果选中价格区间，就添加价格区间过滤
        String skuPrice = param.getSkuPrice();
        if (!StringUtils.isEmpty(skuPrice)) {
            // 0_500/_500/500_
            String[] skuPrices = skuPrice.split("_");
            if (skuPrices.length == 2) {
                boolQueryBuilder.filter(QueryBuilders.rangeQuery("skuPrice").gte(skuPrices[0]).lte(skuPrices[1]));
            } else if (skuPrice.endsWith("_")) {
                boolQueryBuilder.filter(QueryBuilders.rangeQuery("skuPrice").gte(skuPrices[0]));
            } else {
                boolQueryBuilder.filter(QueryBuilders.rangeQuery("skuPrice").lte(skuPrices[0]));
            }
        }

        // 如果属性不为空，添加属性过滤查询条件
        List<String> attrs = param.getAttrs();
        if (attrs != null && attrs.size() > 0) {
            for (String attr : attrs) {
                BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
                // attrs=1_5寸:8寸
                String[] s = attr.split("_");
                String attrId = s[0]; // 属性的id
                String[] valus = s[1].split(":"); // 属性值
                boolQuery.must(QueryBuilders.termsQuery("attrs.attrId", attrId));
                boolQuery.must(QueryBuilders.termsQuery("attrs.attrValue", valus));
                // attrs 指定属性，使用嵌入式属性，过滤、分组、匹配都要嵌入式。 ScoreMode 指明该查询是否要参与评分
                NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery("attrs", boolQuery, ScoreMode.None);

                boolQueryBuilder.filter(nestedQueryBuilder);
            }
        }

        /**
         * 排序、分页、高亮
         */

        // sort=saleCount_asc/desc 排序
        // 如果排序字段不为空，添加排序条件
        String sort = param.getSort();
        if (!StringUtils.isEmpty(sort)) {
            // 分割成 [属性名,属性值]
            String[] s = sort.split("_");
            if ("asc".equals(s[1])) {
                sourceBuilder.sort(s[0], SortOrder.ASC);
            } else {
                sourceBuilder.sort(s[0], SortOrder.DESC);
            }
        }

        // 分页
        // 如果当前页码不为空，添加上当前页码
        Integer pageNum = param.getPageNum();
        sourceBuilder.from((pageNum - 1) * ProductSearchConstant.PRODUCT_PAGESIZE).size(ProductSearchConstant.PRODUCT_PAGESIZE);

        // 高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        // 设置高亮属性
        highlightBuilder.field("skuTitle");
        highlightBuilder.preTags("<b style='color:red'>");
        highlightBuilder.postTags("</b>");
        sourceBuilder.highlighter(highlightBuilder);


        /**
         *  品牌聚合、分类聚合、属性聚合
         */

        // 品牌的聚合
        TermsAggregationBuilder brandAgg = AggregationBuilders.terms("brand_agg").field("brandId");
        ;
        // 根据品牌id进行分组，然后在聚合出品牌id。
        brandAgg.subAggregation(AggregationBuilders.terms("brandNameAgg").field("brandName").size(1));
        // 品牌图片聚合
        brandAgg.subAggregation(AggregationBuilders.terms("brandImgAgg").field("brandImg").size(1));
        sourceBuilder.aggregation(brandAgg);

        // 菜单分类的聚合（分组），将查询出的结果按分类聚合（分组），并聚合出每个分类的名字
        TermsAggregationBuilder catalogAgg = AggregationBuilders.terms("catelogAgg").field("catelogId");
        catalogAgg.subAggregation(AggregationBuilders.terms("catelogNameAgg").field("catalogName"));

        sourceBuilder.aggregation(catalogAgg);

        // 因为attr声明是嵌入式，所以要使用nested声明，构建一个属性聚合构造器 attrAgg聚合名
        NestedAggregationBuilder nestedAgg = AggregationBuilders.nested("attrAgg", "attrs");
        // 以 attrid聚合（分组），聚合名为 attrIdAgg
        TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms("attrIdAgg").field("attrs.attrId");
        // 以attrIdAgg聚合为基础（父聚合)，添加其子聚合。聚合（分组）名为 attrNameAgg，聚合（分组）attrs.attrName 属性，作用到父聚合聚合的每一组数据上
        attrIdAgg.subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"));
        // 以attrIdAgg聚合为基础（父聚合)，添加其子聚合。聚合（分组）名为 attrValueAgg，聚合（分组）attrs.attrValue 属性，作用到父聚合聚合的每一组数据上
        attrIdAgg.subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue"));
        // 指定其子聚合为 attrIdAgg
        nestedAgg.subAggregation(attrIdAgg);
        sourceBuilder.aggregation(nestedAgg);
        System.out.println(sourceBuilder.toString());
        sourceBuilder.query(boolQueryBuilder);

        return request;

    }

    /**
     * 构建用于返回页面的数据
     *
     * @param searchResponse
     * @param param
     * @return
     */
    private SearchResult buildSearchResponse(SearchResponse searchResponse, SearchParam param)  {

        SearchResult result = new SearchResult();
        SearchHits hits = searchResponse.getHits();
        // 1、返回的所有查询到的商品
        SearchHit[] hitsHits = hits.getHits();
        // 1.1）、把命中的记录封装成页面显示的格式（商品集合数组）
        List<SkuEsModel> products = new ArrayList<>(hitsHits.length);
        for (SearchHit hit : hitsHits) {
            // 1.2）、拿到商品的信息，并封装成为页面需要的格式
            String sourceAsString = hit.getSourceAsString();
            SkuEsModel product = JSON.parseObject(sourceAsString, SkuEsModel.class);
            if (!StringUtils.isEmpty(param.getKeyword())) {

                // 1.3）、如果模糊查询关键字不为空，有数据就设置高亮标题
                String skuTitle = hit.getHighlightFields().get("skuTitle").getFragments()[0].toString();
                product.setSkuTitle(skuTitle);
            }

            products.add(product);
        }
        // 设置商品集合
        result.setProducts(products);


        /**
         * 封装聚合出来的数据，属性信息、分类信息、品牌信息。
         */

        Aggregations aggregations = searchResponse.getAggregations();
        // 2、当前所有商品涉及到的所有属性信息
        // 2.1）、获取属性的聚合
        ParsedNested parsedNested = aggregations.get("attrAgg");
        Aggregations attrAgg = parsedNested.getAggregations();
        // 2.2）、获取属性id的分组
        ParsedLongTerms attrIdAgg = attrAgg.get("attrIdAgg");
        // 2.3）、拿到分组之后的一组数据，key是属性id
        List<? extends Terms.Bucket> attrIdAggBuckets = attrIdAgg.getBuckets();
        List<SearchResult.AttrVo> attrVos = new ArrayList<>(attrIdAggBuckets.size());
        if (attrIdAggBuckets.size() > 0) {
            for (Terms.Bucket bucket : attrIdAggBuckets) {
                Aggregations attrIdAggs = bucket.getAggregations();
                // 2.4)、获取属性id
                String attrId = bucket.getKeyAsString();

                // 2.5）、获取属性名的分组，一个属性id对应一个属性名，对应多个属性值
                ParsedStringTerms attrNameAgg = attrIdAggs.get("attrNameAgg");
                String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
                // 2.6）、获取属性值分组数据,
                ParsedStringTerms attrValueAgg = attrIdAggs.get("attrValueAgg");
                // 2.6.1）、属性值有多个，拿到属性分组，遍历，封装集合
                List<? extends Terms.Bucket> attrValuesBkt = attrValueAgg.getBuckets();
                ArrayList<String> values = new ArrayList<>(attrValuesBkt.size());
                for (Terms.Bucket bucket1 : attrValuesBkt) {
                    String attrValue = bucket1.getKeyAsString();
                    values.add(attrValue);
                }
                // 2.7）、创建用于页面使用的vo，封装数据
                SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
                attrVo.setAttrId(Long.parseLong(attrId));
                attrVo.setAttrName(attrName);
                attrVo.setAttrValue(values);
                attrVos.add(attrVo);
            }
        }
        result.setAttrs(attrVos);

        // 3、当前所有商品涉及到的所有分类信息
        // 3.1）、获取分类聚合
        ParsedLongTerms catelogAgg = aggregations.get("catelogAgg");
        // 3.2）、拿到按照分类分组之后的数据
        List<? extends Terms.Bucket> buckets = catelogAgg.getBuckets();
        List<SearchResult.CatelogVo> catelogVos = new ArrayList<>(buckets.size());
        if (buckets.size() > 0) {
            // 遍历封装
            for (Terms.Bucket bucket : buckets) {
                // 3.3)、获取分类分组之后的分类id
                String catelogId = bucket.getKeyAsString();
                // 3.4）、拿到分类之后的分组名
                ParsedStringTerms catelogNameAgg = bucket.getAggregations().get("catelogNameAgg");
                // 3.5）、按照分类id分组，一个分类id对应一个分类名。获取分组名
                String catelogName = catelogNameAgg.getBuckets().get(0).getKeyAsString();
                // 3.6）、创建用于页面返回的视图模型，并封装
                SearchResult.CatelogVo catelogVo = new SearchResult.CatelogVo();
                catelogVo.setCatelogId(Long.parseLong(catelogId));
                catelogVo.setCatelogName(catelogName);
                catelogVos.add(catelogVo);
            }
        }
        result.setCatelogs(catelogVos);

        // 4、当前所有商品涉及到的所有品牌信息
        // 4.1）、拿到按照品牌分组的聚合
        ParsedLongTerms brand_agg = aggregations.get("brand_agg");
        // 4.2)、拿到分组之后的数据用于遍历封装
        List<? extends Terms.Bucket> brandAggBuckets = brand_agg.getBuckets();
        List<SearchResult.BrandVo> brandVos = new ArrayList<>(brandAggBuckets.size());
        if (brandAggBuckets.size() > 0) {
            // 遍历封装
            for (Terms.Bucket brandAggBucket : brandAggBuckets) {
                // 4.3）、因为品牌分组是按照品牌的id分组，所以直接获取分类id
                String brandId = brandAggBucket.getKeyAsString();
                // 4.4）、获取按照每个品牌id分组的聚合
                Aggregations aggregationsBrand = brandAggBucket.getAggregations();
                // 4.5）、获取每个品牌的logo，一个品牌id对应一个品牌logo和一个品牌名，所以直接获取第一个就可以拿到数据
                ParsedStringTerms brandImgAgg = aggregationsBrand.get("brandImgAgg");
                String brandImg = brandImgAgg.getBuckets().get(0).getKeyAsString();
                // 4.6）、获取每个品牌的品牌名
                ParsedStringTerms brandNameAgg = aggregationsBrand.get("brandNameAgg");
                String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();
                // 4.7）、封装成页面需要的模型，添加到集合，封装，返回
                SearchResult.BrandVo brandVo = new SearchResult.BrandVo();
                brandVo.setBrandId(Long.parseLong(brandId));
                brandVo.setBrandName(brandName);
                brandVo.setBrandImg(brandImg);
                brandVos.add(brandVo);
            }
        }
        result.setBrands(brandVos);

        // 5、分页信息-页码
        Integer pageNum = param.getPageNum();
        result.setPageNum(pageNum);
        // 5、分页信息-总记录数
        long total = hits.getTotalHits().value;
        result.setTotal(total);
        // 5、分页信息-总页码
        // 计算多少页
        int pageSize = (int) total % ProductSearchConstant.PRODUCT_PAGESIZE == 0 ? (int) total / ProductSearchConstant.PRODUCT_PAGESIZE : (int) total / ProductSearchConstant.PRODUCT_PAGESIZE + 1;
        result.setTotalPages(pageSize);

        /**
         * 封装页码
         */
        List<Integer> navs = new ArrayList<>();
        for (int i = 1; i <= pageSize; i++) {
            navs.add(i);
        }
        result.setPageNavs(navs);

        /**
         * 面包屑的封装
         */
        // 属性面包屑的封装
        List<String> attrs = param.getAttrs();
        if (attrs != null && attrs.size() > 0) {
            List<SearchResult.NavVo> navVos = attrs.stream().map(item -> {
                SearchResult.NavVo navVo = new SearchResult.NavVo();
                //  attrs= 1_5寸:8寸
                String[] attr = item.split("_");
                long attrId = Long.parseLong(attr[0]);
                result.getAttrIds().add(attrId);
                // 从远程商品服务根据属性id获取属性名
                R r = productRemoteService.getAttrName(attrId);
                // 如果远程服务出现宕机或网络波动，默认设置属性id为属性铭
                if (r.getCode() == 0) {
                    String attrName = (String) r.get("attrName");
                    navVo.setNavName(attrName);
                } else {
                    navVo.setNavName(attr[1]);
                }
                navVo.setNavValue(attr[1]);

                String replace = replaceUrl(item, param.get_queryString(), "");

                navVo.setLink("http://search.gulimall.com/list.html?" + replace);

                return navVo;
            }).collect(Collectors.toList());
            result.setNavs(navVos);
        }
        if (param.getBrandId() != null && param.getBrandId().size() > 0) {
            // 品牌面包屑的封装
            List<SearchResult.NavVo> voList = param.getBrandId().stream().map(item -> {
                R r = productRemoteService.getBrandName(item);
                String brandName = (String) r.get("brandName");
                SearchResult.NavVo navVo = new SearchResult.NavVo();
                navVo.setNavName(brandName);
                String replace = replaceUrl(param.get_queryString(), brandName, "");

                navVo.setLink("http://search.gulimall.com/list.html?" + replace);
                return navVo;
            }).collect(Collectors.toList());

            result.getNavs().addAll(voList);
        }

        return result;
    }

    public String replaceUrl(String name, String url, String replaceStr) {
        String encode = null;
        try {
            encode = URLEncoder.encode(name, "UTF-8");
            encode = encode.replace("+", "%20");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        String replace = "";

        if (url.contains("&attrs=" + encode)) {
            replace = url.replace("&attrs=" + encode, "");
        } else if (url.contains("attrs=" + encode)) {
            replace = url.replace("attrs=" + encode, "");
        }
        return replace;
    }

}
